/**
 * WTF Engine
 *
 * License... etc.
 **
 * Boost/ASIO Network Implementation
 **
 * Author: Sean Micklethwaite
 **/

#ifndef __net_asioH__
#define __net_asioH__

#include "config.h"

#ifdef __NET_ASIO__

#include "net_socket.h"
#include "loggable.h"
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <vector>
#include <deque>

namespace WtfEngine {
	/**
	 * Generic socket class. Methods are implemented for either
	 * TCP or UDP.
	 **/
	class Socket: public virtual ISocket, public GcObject
	{
		protected:
			static const unsigned MaxPacketSize = 8192;
			typedef unsigned char *	tRecvBuffer;

			tStatus			mStatus;

			tRecvBuffer					mvBuffer;
			std::deque<IPacket::tRef>	mvSendQueue;
			std::deque<IPacket::tRef>	mvRecvQueue;
			
			void SetStatus(tStatus st);

		public:
			Socket();
			virtual ~Socket();
			
			virtual void Listen();
			virtual void Connect(tIpAddress ip);
			virtual void Disconnect();
			
			IPacket::tRef	RecvPacket();

			tStatus getStatus() {
				return mStatus;
			};

			virtual void Destroy();

			/**** ASIO Specific ****/

			/// Called when a connection is initiated
			virtual void OnConnect(const boost::system::error_code &error);
			/// Called to indicate that data has been received by a socket
			virtual void OnReceive(const boost::system::error_code& error, std::size_t n);
			/// Called to indicate that data has been sent asynchronously
			virtual void OnSend(const boost::system::error_code& error);

			GC_AUTOSIZE(Socket);
	};
	
	
	/**
	 * TCP guarantees the correctness of packets sent, at
	 * a cost of performance.
	 **/
	class TcpSocket: public Socket
	{
		GC_OBJECT(TcpSocket, GC_ADDEDSIZE_AUTO(Socket, TcpSocket));
		
	private:
		struct Server {
			boost::asio::ip::tcp::acceptor	Acceptor;
			TcpSocket::tRef					rClientSocket;

			Server(tPort port);
			~Server();

			/// Listens for client connections
			void Listen(TcpSocket * pSocket);
		};
		struct Client {
			boost::asio::ip::tcp::socket	Socket;
			boost::asio::ip::tcp::endpoint	Endpoint;

			Client();
			~Client();

			/// Receives data from the endpoint
			void Listen(TcpSocket * pSocket);
			/// Sends data to the endpoint
			void Send(TcpSocket * pSocket, const IPacket::tRef& rPacket);
		};

		union {
			/// Used when the socket is listening
			Server *	mpServer;
			/// Used when the socket is connected to an endpoint
			Client *	mpClient;
		};

		tPort						mPort;
		
		public:
			TcpSocket(tPort port);
			~TcpSocket();
			
			void Listen();
			void Connect(tIpAddress ip);
			void Disconnect();
			
			void			SendPacket(const IPacket::tRef& rPacket);
		
			tIpAddress	getIpAddress() {
				return (mStatus == LISTENING ? tIpAddress(mpServer->Acceptor.local_endpoint().address().to_v4().to_ulong()) :
					(mpClient ? tIpAddress(mpClient->Endpoint.address().to_v4().to_ulong()) : tIpAddress(0)));
			};
			tPort		getPort() {
				return mPort;
			};

			virtual void OnConnect(const boost::system::error_code &error);
			virtual void OnClientConnect(const boost::system::error_code& error);
			virtual void OnReceive(const boost::system::error_code& error, std::size_t n);
			virtual void OnSend(const boost::system::error_code& error);
	};
	
	
	/**
	 * UDP provides unreliable, but faster transmission of packets. They may
	 * arrive damaged, out of order, or not at all.
	 **/
	class UdpSocket: public Socket
	{
		boost::asio::ip::udp::socket	mSocket;
		boost::asio::ip::udp::endpoint	mEndpoint;

		public:
			UdpSocket(tPort port);
			~UdpSocket();
			
			virtual void Listen();
			virtual void Connect(tIpAddress ip);
			virtual void Disconnect();

			virtual void SendPacket(const IPacket::tRef& rPacket);

			virtual void OnReceive(const boost::system::error_code& error, std::size_t n);
			virtual void OnSend(const boost::system::error_code& error);

			tIpAddress	getIpAddress() {
				return mEndpoint.address().to_v4().to_ulong();
			};
			tPort		getPort() {
				return mEndpoint.port();
			};
		
		GC_OBJECT(UdpSocket, GC_ADDEDSIZE_AUTO(Socket, UdpSocket));
	};
	
	/**
	 * UDP Broadcast Socket - for broadcasting over a subnet.
	 **/
	class UdpBroadcastSocket: public UdpSocket
	{
		public:
			UdpBroadcastSocket(tPort port);

			void Listen() {
				throw new std::logic_error("Network: cannot listen on UDP broadcast socket");
			};
			void Connect(tIpAddress ip) {
				throw new std::logic_error("Network: cannot connect UDP broadcast socket");
			};
			IPacket::tRef	RecvPacket() {
				throw new std::logic_error("Network: cannot receive from UDP broadcast socket");
			};

		GC_OBJECT(UdpBroadcastSocket, GC_ADDEDSIZE_AUTO(UdpSocket, UdpBroadcastSocket));
	};
	

	/**
	 * Network Manager - keeps a list of active sockets, and polls the sockets for
	 * activity.
	 **/
	class NetworkManager : public Task<NetworkManager>, public Loggable<NetworkManager> {
		boost::asio::io_service mIoService;
		std::set<ISocket::tRef>	mSockets;

		public:
			NetworkManager();

			void AddSocket(const ISocket::tRef& rSock) {
				mSockets.insert(rSock);
			};
			void RemoveSocket(const ISocket::tRef& rSock) {
				mSockets.erase(rSock);
			};

			/**** ASIO Specific ****/

			boost::asio::io_service& getIoService() {
				return mIoService;
			};

		protected:
			/// Reads incoming packets, firing events for them.
			void Run();
	};
};

#endif // __NET_ASIO__
#endif
